// 选择器
const ATTR_CONTEXT = '.touch-context';
const ATTR_ITEM = '.touch-item';
const ATTR_EXPAND_UP = '.touch-expand-up';
const ATTR_EXPAND_DOWN = '.touch-expand-down';
const ATTR_SCROLL = '.touch-scroll';

// 时间样式引起的偏移(时间文字在边框线上)
let hourOffset;

// 事件类型
let eventType;
const typeMap = {
    MOVE: 'move',
    EXPAND_UP: 'expandUp',
    EXPAND_DOWN: 'expandDown'
};

// 各种偏移量
const adjust = {
    x: 0, // target初始x
    y: 0, // target初始y
    h: 0, // target初始高度
    t: 0, // 距离context的上边距
    b: 0, // 距离context的下边距
    s: 0, // 当前滚动高度
    sh: 0 // 初始scrollHeight,拖动会影响
};

// 滚动dom
let scrollDom;

// 是否在拖动中
let touching = false;

// 是否有效点击 位移大于1px才是有效的
let validTouch = false;

// 上下文dom
let context;

// 最小分钟时间
let minMinute;

// 暴露的事件回调
const callbackMap = {
    onChange: null,
    onComplete: null
};

// 挂载拖动的目标属性
const target = {
    dom: null,
    index: null,
    offset: null
};

// 封装一下写style方法
const setStyle = (dom, opt) => {
    Object.keys(opt).forEach(key => {
        // eslint-disable-next-line no-param-reassign
        dom.style[key] = opt[key];
    });
};

// 获取手指坐标，如果是pc端可以兼容一下
const getPos = evt => {
    const touches = evt.changedTouches;
    const [{ pageX, pageY }] = touches;
    return [pageX, pageY];
};

// 移动的处理方法
const moveItem = () => {
    setStyle(target.dom, {
        transform: `translateY(${target.offset}px)`
    });
};

// 缩放的处理方法
const expandItem = type => {
    if (type === typeMap.EXPAND_UP) {
        setStyle(target.dom, {
            transform: `translateY(${target.offset}px)`,
            height: `${adjust.h - target.offset}px`
        });
    }
    if (type === typeMap.EXPAND_DOWN) {
        setStyle(target.dom, {
            height: `${adjust.h + target.offset}px`
        });
    }
};

const isToBottom = () => scrollDom.scrollTop >= adjust.sh;

// 移动&缩放时到达上下边界，滚动条进行滚动
const scrollContext = () => {
    if (!scrollDom) return;

    // 往下
    if (target.offset > 0) {
        // 滚动到底部
        if (isToBottom()) {
            scrollDom.scrollTop = adjust.sh;
            return;
        }

        let offsetBottom;
        if (eventType === typeMap.EXPAND_UP) {
            offsetBottom = target.offset + adjust.t - scrollDom.offsetHeight;
        } else {
            offsetBottom = target.offset - adjust.b;
        }
        scrollDom.scrollTop = adjust.s + (offsetBottom > 0 ? offsetBottom : 0);
    }
    // 往上
    if (target.offset < 0) {
        let offsetTop;
        if (eventType === typeMap.EXPAND_DOWN) {
            offsetTop = target.offset + scrollDom.offsetHeight - adjust.b;
        } else {
            offsetTop = target.offset + adjust.t;
        }
        scrollDom.scrollTop = adjust.s + (offsetTop < 0 ? offsetTop : 0);
    }
};

// 根据点击的target来判断事件类型
const getEventType = evt => {
    if (target.dom.querySelector(ATTR_EXPAND_UP).contains(evt.target)) {
        return typeMap.EXPAND_UP;
    }
    if (target.dom.querySelector(ATTR_EXPAND_DOWN).contains(evt.target)) {
        return typeMap.EXPAND_DOWN;
    }
    return typeMap.MOVE;
};

// 获取target在滚动容器中可见范围内的上下间隔
const getSpace = () => {
    const { y: itemY } = target.dom.getBoundingClientRect();
    const { y: contextY } = (scrollDom || context).getBoundingClientRect();
    return [itemY - contextY, contextY + scrollDom.offsetHeight - itemY - target.dom.offsetHeight];
};

// 处理点击开始
const handleStart = evt => {
    // 委托代理
    const dragItems = Array.from(context.querySelectorAll(ATTR_ITEM));
    target.index = dragItems.findIndex(item => item.contains(evt.target));
    target.dom = dragItems[target.index];
    if (!target.dom) return;

    eventType = getEventType(evt);

    const [x, y] = getPos(evt);

    adjust.x = x;
    adjust.y = y;
    adjust.h = target.dom.offsetHeight;

    const [t, b] = getSpace();
    adjust.t = t;
    adjust.b = b;
    adjust.s = scrollDom?.scrollTop;

    touching = true;
};

// 获取事件对应handler
const moveHandleMap = {
    [typeMap.MOVE]: moveItem,
    [typeMap.EXPAND_DOWN]: () => expandItem(typeMap.EXPAND_DOWN),
    [typeMap.EXPAND_UP]: () => expandItem(typeMap.EXPAND_UP)
};

// 判断是否有效点击
const checkValidTouch = offset => Math.abs(offset) > 1;

// 限制移动范围
const restrictOffset = offset => {
    // 往下
    if (offset > 0) {
        let standard;
        if (eventType === typeMap.EXPAND_UP) {
            standard = adjust.h;
            if (minMinute) standard -= minMinute;
        } else {
            standard = adjust.b - adjust.s + adjust.sh;
        }

        if (offset >= standard) return standard;
    }

    // 往上
    if (offset < 0) {
        let standard;
        if (eventType === typeMap.EXPAND_DOWN) {
            standard = adjust.h;
            if (minMinute) standard -= minMinute;
        } else {
            standard = adjust.t + adjust.s - hourOffset;
        }

        if (-offset >= standard) return -standard;
    }
    return offset;
};

// 限制刻度,但是UI-DATA的交互并不精确,需要两侧都做调整,这里紧紧是UI
const limitScaleOffset = offset => {
    if (!minMinute) return offset;
    // return offset;
    return Math.floor(offset / minMinute) * minMinute;
};

// 处理移动以及回调
const handleMove = evt => {
    if (!target.dom || !touching) return;
    evt.preventDefault();

    const [, y] = getPos(evt);
    const offset = y - adjust.y;
    validTouch = checkValidTouch(offset);
    if (!validTouch) return;

    let nextOffset = restrictOffset(offset);
    nextOffset = limitScaleOffset(nextOffset);

    target.offset = nextOffset;

    moveHandleMap[eventType]();
    scrollContext();

    if (callbackMap.onChange) {
        callbackMap.onChange(target.index, target.offset, eventType);
    }
};

// 处理移动后的状态重置以及回调
const handleEnd = () => {
    if (!validTouch) return;

    touching = false;
    validTouch = false;
    if (callbackMap.onComplete) {
        callbackMap.onComplete(target.index, target.offset, eventType);
        setStyle(target.dom, {
            transform: ''
        });
    }
    target.dom = null;
};

// 绑定事件
const addEvent = () => {
    context.addEventListener('touchstart', handleStart);
    context.addEventListener('touchmove', handleMove);
    context.addEventListener('touchend', handleEnd);
};

// 处理拖动造成的scrollHeight变化
const resize = () => {
    setTimeout(() => {
        adjust.sh = scrollDom?.scrollHeight - scrollDom?.clientHeight;
    });
};

// 动态设置option
const setOption = (option = {}) => {
    if (context) {
        callbackMap.onChange = option.onChange;
        callbackMap.onComplete = option.onComplete;
        hourOffset = option.cellHeight;
        minMinute = (option.minMinute / 60) * hourOffset;
    }
};

// 初始化
const initialize = (option = {}) => {
    context = document.querySelector(ATTR_CONTEXT);
    if (context) {
        addEvent();
        scrollDom = document.querySelector(ATTR_SCROLL);
        setOption(option);
        resize();
    }
};

// 销毁事件绑定
const destroy = () => {
    if (!context) return;
    context.removeEventListener('touchstart', handleStart);
    context.removeEventListener('touchmove', handleMove);
    context.removeEventListener('touchend', handleEnd);
};

export { initialize, destroy, setOption, resize };
